//
// Time-stamp: <94/12/01 20:23:30 stephan>
//
//	MiscClassDecoder.m -- an Object subclass that is able to analyze
//	the definition of an Objective-C class.
//	
//		Written by Stephan Wacker <stephan@rodion.muc.de>
//		Copyright (c) 1994 by Stephan Wacker.
//		Version 1.0  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	


#import "MiscClassDecoder.h"
// #import <misckit/MiscClassDecoder.h>

#import <objc/objc-runtime.h>
#import <ctype.h>


#define K_IndentInstanceVar	4
#define K_IndentStructMember	2


//====<   Private methods   >====//

@interface MiscClassDecoder ( Private )

- clearText;
- appendText: (const char *) aString, ...;
- appendLine: (const char *) aString, ...;
- finishOutput;

- (const char *) showType: (const char *) aTypeString;
- showMethod: (Method) aMethod prefix: (char *) prefix;

- showInheritance: (Class) aClass;
- showProtocols: (Class) aClass;
- showInstanceVars: (Class) aClass;
- showInstanceMethods: (Class) aClass;
- showClassMethods: (Class) aMetaclass;

- setText: aText;

+ initialize;
- read: (NXTypedStream *) stream;
- write: (NXTypedStream *) stream;

@end // MiscClassDecoder ( Private )


//====<   Auxiliary functions   >====//

static
const char * type_name( char type )
// Decode single character `type'.
{
    const char	*result = NULL;
    
    switch( type )
    {
	case _C_ID	: result = "id";		break;
	case _C_CLASS	: result = "Class";		break;
	case _C_SEL	: result = "SEL";		break;
	case _C_CHR	: result = "char";		break;
	case _C_UCHR	: result = "unsigned char";	break;
	case _C_SHT	: result = "short";		break;
	case _C_USHT	: result = "unsigned short";	break;
	case _C_INT	: result = "int";		break;
	case _C_UINT	: result = "unsigned int";	break;
	case _C_LNG	: result = "long";		break;
	case _C_ULNG	: result = "unsigned long";	break;
	case _C_FLT	: result = "float";		break;
	case _C_DBL	: result = "double";		break;
	case _C_VOID	: result = "void";		break;
	case _C_UNDEF	: result = "<undef>";		break;
	case _C_CHARPTR	: result = "char *";		break;
    }
    
    return result;

} // type_name()


static
const char * struct_name( const char *name, int len )
// Check for well-known struct names.
{
#define M_TEST_UNDERSCORE( type ) \
	if( ! strncmp( name, "_" #type, len ) ) return (#type)
    
    switch( len )
	// Add other common names to this list.
    {
      case 6:
	M_TEST_UNDERSCORE( NXFSM );
	M_TEST_UNDERSCORE( NXLay );
	M_TEST_UNDERSCORE( NXRun );
	break;
      case 7:
	M_TEST_UNDERSCORE( NXRect );
	M_TEST_UNDERSCORE( NXSize );
	M_TEST_UNDERSCORE( NXZone );
	break;
      case 8:
	M_TEST_UNDERSCORE( NXChunk );
	M_TEST_UNDERSCORE( NXColor );
	M_TEST_UNDERSCORE( NXEvent );
	M_TEST_UNDERSCORE( NXPoint );
	M_TEST_UNDERSCORE( NXSelPt );
	break;
      case 9:
	M_TEST_UNDERSCORE( NXScreen );
	M_TEST_UNDERSCORE( NXStream );
	break;
      case 10:
	M_TEST_UNDERSCORE( NXDefault );
	M_TEST_UNDERSCORE( NXHandler );
	M_TEST_UNDERSCORE( NXLayInfo );
	M_TEST_UNDERSCORE( NXMessage );
	M_TEST_UNDERSCORE( NXTabStop );
	break;
      case 11:
	M_TEST_UNDERSCORE( NXFaceInfo );
	M_TEST_UNDERSCORE( NXLayArray );
	M_TEST_UNDERSCORE( NXResponse );
	M_TEST_UNDERSCORE( NXRunArray );
	break;
      case 12:
	M_TEST_UNDERSCORE( NXCharArray );
	M_TEST_UNDERSCORE( NXTextBlock );
	M_TEST_UNDERSCORE( NXTextCache );
	M_TEST_UNDERSCORE( NXTextStyle );
	break;
      case 13:
	M_TEST_UNDERSCORE( NXBreakArray );
	M_TEST_UNDERSCORE( NXHeightInfo );
	M_TEST_UNDERSCORE( NXWidthArray );
	break;
      case 14:
	M_TEST_UNDERSCORE( NXAcknowledge );
	M_TEST_UNDERSCORE( NXFontMetrics );
	break;
      case 15:
	M_TEST_UNDERSCORE( NXHeightChange );
	M_TEST_UNDERSCORE( NXModalSession );
	M_TEST_UNDERSCORE( NXRemoteMethod );
	break;
      case 16:
	M_TEST_UNDERSCORE( NXTrackingTimer );
	break;
    }
    
    return NULL;
    
#undef M_TEST_UNDERSCORE

} // structName()


@implementation MiscClassDecoder


//====<   Writing text   >====//

- clearText
// Delete contents of `text'.
{
    [text selectAll: self];
    [text delete: self];

    indent = 0, bol = YES, lineCount = 0;

    return self;

} // clearText


- appendText: (const char *) aString, ...
// Append `aString' to the contents of `text'.
{
    va_list	ap;
    char	buf[4000], *buf_p = buf;
    int		pos;
    
    pos = [text textLength];
    [text setSel: pos : pos];

    if( bol )
    {
	sprintf( buf_p, "%*s", indent, "" );
	buf_p += indent;
	bol = NO;
    }

    va_start(ap, aString);
    vsprintf( buf_p, aString, ap );
    [text replaceSel: buf];
    
    return self;

} // appendText:...


- appendLine: (const char *) aString, ...
// Append `aString' to `text' and start a new line.
{
    va_list	ap;
    char	buf[4000], *buf_p = buf;
    int		pos;
    
    pos = [text textLength];
    [text setSel: pos : pos];
    
    if( bol )
    {
	sprintf( buf_p, "%*s", indent, "" );
	buf_p += indent;
    }
    else
    {
	bol = YES;
    }

    va_start(ap, aString);
    vsprintf( buf_p, aString, ap );
    strcat( buf_p, "\n" );
    [text replaceSel: buf];

    if( ++ lineCount == 20 )
    {
	[[text window] disableDisplay];
	[text setNeedsDisplay: YES];
    }
    
    return self;

} // appendLine:...


- finishOutput
// Final actions after all text has been output.
{
    [classNameField selectText: self];
    
    if( ! [[text window] isDisplayEnabled] )
    {
	[[[text window] reenableDisplay] display];
    }

    return self;
} // finishOutput

//====<   Decoding type specifications   >====//

- (const char *) showType: (const char *) aTypeString
// Recursive interpretation of the encoded `aTypeString'.
// Return position after analyzed substring.
{
    const char	*typeName;
    const char	*p = aTypeString;
    const char	*pe;
    const char	*memberName;
    int		i;
    
    switch( *p )
    {
      case _C_PTR:		// Pointer
	p = [self showType: p+1];
	[self appendText: " *"];
	break;
	
      case _C_BFLD:		// Bitfield
	for( ++p, i = 0; isdigit(*p); p++ )
	{
	    i = i * 10 + (*p - '0');
	}
	// We don't really know the bitfield's type,
	// but `unsigned int' is a good guess.
	[self appendText: "unsigned int ... : %d", i];
	break;
	
      case _C_ARY_B:		// Array begin
	for( ++p, i = 0; isdigit(*p); p++ )
	{
	    i = i * 10 + (*p - '0');
	}
	p = [self showType: p];
	[self appendText: " ... [%d]", i];
	// TODO: correct syntax

	if( *p == _C_ARY_E )	// Array end
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	}
	break;
	
	
      case _C_STRUCT_B:		// Struct begin
	for( pe = ++p; *pe && *pe != '=' && *pe != _C_STRUCT_E; pe++ )
	{
	    // keep on going
	}
	    
	if( (typeName = struct_name( p, pe-p )) != NULL )
				// it's a well-known struct
	{
	    [self appendText: "%s", typeName];
	    for( p = pe-1, i = 1; *++p && i > 0; ) {
		switch( *p ) {
		  case _C_STRUCT_B: i++; break;
		  case _C_STRUCT_E: i--; break;
		}
	    } // for p
	}
	else
	{
	    [self appendText: "struct %.*s", pe-p, p];
	    if( *(p=pe) == '=' ) {
		[self appendLine: " {"];
		indent += K_IndentStructMember;
		for( i = 1, ++p; *p && *p != _C_STRUCT_E; i++ )
		{
		    if( *p == '"' )
		    {
			for( memberName = pe = p+1; *pe && *pe != '"'; pe++ )
			{
			    // keep on going
			}
			p = pe+1;
		    }
		    else
		    {
			memberName = NULL;
		    }
		    p = [self showType: p];
		    if( memberName )
		    {
			[self appendLine: " %.*s;",
			 pe-memberName, memberName];
		    }
		    else
		    {
			[self appendLine: " member_%d;", i];
		    }
		}
		indent -= K_IndentStructMember;
		[self appendText: "}"];
	    }
	}

	if( *p == _C_STRUCT_E )	// Struct end
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	}
	break;
	
	
      case _C_UNION_B:		// Union begin
	[self appendLine: "union {"];
	indent += K_IndentStructMember;
	for( i = 1, ++p; *p && *p != _C_UNION_E; i++ )
	{
	    p = [self showType: p];
	    [self appendLine: " case_%d;", i];
	}
	indent -= K_IndentStructMember;
	[self appendText: "}"];
	
	if( *p == _C_UNION_E )
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	}
	break;
	
	
      default:
	if( ! (typeName = type_name( *p )) )
	{
	    [self appendText: "<?: %c>", *p ];
	}
	else
	{
	    [self appendText: "%s", typeName];
	}
	++ p;
	break;
    
    } // switch( first char of aTypeString )
    
    return p;

} // showType:


- showMethod: (Method) aMethod prefix: (char *) prefix
// Show declaration of `aMethod'.
// `prefix' is either "-" or "+".
{
    const char	*name = sel_getName( aMethod->method_name );
    const char	*p, *pe;
    int		nArgs = method_getNumberOfArguments( aMethod );
    int		i;
    const char	*type;
    int		offset;
    
    
    if( prefix )
    {
	[self appendText: "%s ", prefix];
    }
    
    if( nArgs <= 2 )
    {
	[self appendLine: "%s;", name];
    }
    else
    {
	for( i = 2, p = name; i < nArgs; i++, p = pe )
	{
	    for( pe = p; *pe && *pe++ != ':'; )
	    {
		// keep on going
	    }
	    [self appendText: "%s%.*s", (i == 2 ? "" : " "), pe-p, p];
	    
	    method_getArgumentInfo( aMethod, i, &type, &offset );

	    // Don't print default return type `id'.
	    if( *type != _C_ID )
	    {
		[self appendText: " ("];
		[self showType: type];
		[self appendText: ")"];
	    }
	    [self appendText: " arg_%d", i-1];

	} // for i, p
	
	[self appendLine: ";"];
    }

    return self;

} // showMethod:prefix:


//====<   Showing the class definition   >====//

- showInheritance: (Class) aClass
// Show name of `aClass' and its superclass.
{
    Class	superClass = [aClass superclass];
    
    [self appendText: "%s", [aClass name]];
    if( superClass )
    {
	[self appendLine: " : %s", [superClass name]];
    }
    else
    {
	[self appendLine: " // root class"];
    }
    
    return self;

} // showInheritance:


- showProtocols: (Class) aClass
// Show all protocols adopted by `aClass'.
{
    if( ! aClass->protocols || aClass->protocols->count == 0 )
    {
	// do nothing
    }
    else
    {
	int		i;
	Protocol	**pp = aClass->protocols->list;
	
	[self appendText: "  < %s", [*pp name]];
	
	for( i = 1, pp++; i < aClass->protocols->count; i++, pp++ )
	{
	    [self appendText: ", %s", [*pp name]];
	}
	
	[self appendLine: " >"];
    }
    
    return self;

} // showProtocols:


- showInstanceVars: (Class) aClass
// Show definition of all instance variables defined in `aClass'.
{
    [self appendLine: "{"];
    indent += K_IndentInstanceVar;
    
    if( ! aClass->ivars )
    {
	[self appendLine: "// no instance variables"];
    }
    else
    {
	int	i;
	Ivar	ivp = aClass->ivars->ivar_list;
	
	for( i = 0; i < aClass->ivars->ivar_count; i++, ivp++ )
	{
	    // [self appendLine: "// %s", ivp->ivar_type];
	    [self showType: ivp->ivar_type];
	    [self appendLine: " %s;", ivp->ivar_name];
	} // for i
    }
    
    indent -= K_IndentInstanceVar;
    [self appendLine: "}"];
    
    return self;

} // showInstanceVars:


- showInstanceMethods: (Class) aClass
// Show declaration of all instance methods defined in `aClass'.
{
    if( ! aClass->methods )
    {
	[self appendLine: "// no instance methods"];
    }
    else
    {
	struct objc_method_list	*mlp;
	int	i, m;
	Method	mp;
	
	for( m = 1, mlp = aClass->methods;
	     mlp != NULL;
	     m++, mlp = mlp->method_next )
	{
	    [self appendLine: ""];
	    [self appendLine: "// Instance method block #%d holds %d method%s",
	     m, mlp->method_count, (mlp->method_count == 1 ? "" : "s")];

	    for( i = 0, mp = mlp->method_list;
	         i < mlp->method_count;
		 i++, mp++ )
	    {
		[self showMethod: mp prefix: "-"];
	    } // for i
	} // for m, mlp
    }
    
    return self;

} // showInstanceMethods:


- showClassMethods: (Class) aMetaclass
// Show declaration of all class methods defined in aMetaclass.
{
    if( ! aMetaclass->methods )
    {
	[self appendLine: "// no class methods"];
    }
    else
    {
	struct objc_method_list	*mlp;
	int	i, m;
	Method	mp;
	
	for( m = 1, mlp = aMetaclass->methods;
	     mlp != NULL;
	     m++, mlp = mlp->method_next )
	{
	    [self appendLine: ""];
	    [self appendLine: "// Class method block #%d holds %d method%s",
	     m, mlp->method_count, (mlp->method_count == 1 ? "" : "s")];

	    for( i = 0, mp = mlp->method_list;
	         i < mlp->method_count;
		 i++, mp++ )
	    {
		[self showMethod: mp prefix: "+"];
	    } // for i
	} // for m, mlp
    }
    
    return self;

} // showClassMethods:


//====<   Actions   >====//

- analyze: sender
// Analyze class specified in `classNameField'.
{
    const char	*className = [classNameField stringValue];
    Class	class;
    
    
    [self clearText];
    
    if( !(class = objc_lookUpClass( className )) )
    {
	[self appendLine: "No class named \"%s\".", className];
    }
    else
    {
	[self appendLine: "// Definition of class %s, Version %d",
	 className, class_getVersion(class)];
	[self appendLine: ""];
	[self appendText: "@interface "];
	[self showInheritance: class];
	[self showProtocols: class];
	[self showInstanceVars: class];
	[self appendLine: ""];
	[self showClassMethods: class->isa];
	[self appendLine: ""];
	[self showInstanceMethods: class];
	[self appendLine: ""];
	[self appendLine: "@end"];
    }

    [self finishOutput];
    
    return self;

} // analyze:


- list: sender
// List all known classes.
{
    NXHashTable	*classes = objc_getClasses();
    NXHashState	state = NXInitHashState(classes);
    Class	aClass;
    

    [self clearText];
    
    while( NXNextHashState(classes, &state, (void **)&aClass) )
    {
	[self appendLine: "%s", [aClass name]];
    }

    [self finishOutput];
    
    return self;

} // list:


//====<   Services   >====//

- analyzeClass: (const char *) className
// Analyze class given in `className'.
{
    [classNameField setStringValue: className];
    return [self analyze: self];
}


//====<   Outlet connections   >====//

- setText: aText
// Connect to `aText' and set its attributes.
{
    text = aText;
    
    [text setNoWrap];
    [text setMonoFont: YES];
    
    return self;

} // setText:


//====<   Archiving   >====//

+ initialize
{
    if( self == [MiscClassDecoder class] )
    {
	[self setVersion: K_MiscClassDecoderVersion];
    }

    return self;

} // +initialize


- read: (NXTypedStream *) stream
{
    int version;
    
    [super read: stream];
    
    version = NXTypedStreamClassVersion( stream, "MiscClassDecoder" );
    
    switch( version )
    {
      case 1:
	NXReadTypes( stream,
		     K_MiscClassDecoderRWformat_V1,
		     K_MiscClassDecoderRWvars_V1 );
	break;

      default:
	classNameField = nil;
	text = nil;
	break;
    }

    // Initialize non-archived instance variables.
    //
    indent = 0;
    bol = FALSE;
    lineCount = 0;
    
    return self;

} // read:


- write: (NXTypedStream *) stream
{
    [super write: stream];
    
    NXWriteTypes( stream,
		  K_MiscClassDecoderRWformat_V1,
		  K_MiscClassDecoderRWvars_V1 );
    
    return self;
} // write:

@end	// MiscClassDecoder
